iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Build on Google AI

LOCAL:30 天打造 LINE × Google AI 地方服務 Agent系列 第 24 篇

Day 24|改了一行,20 題還過嗎?Google AI 實測牆與 A/B 成本量測

  • 分享至 

  • xImage
  •  

想把活動說明改得親切,不能順便把未知說成保證。今天把 9 題真實路由、6 列思考預算對照與 20 題地方契約回歸分開驗收,先把問句、工具與版本固定下來(凍結),之後不再更動。本輪歷經 Schema 與模型存取政策變更排錯,實際完成 15 次 API 請求;9 題路由全數通過,6 列對照記錄不同思考預算下的延遲與用量差異,不把失敗補成成功。

A friendlier prompt must not turn missing information into a promise. This chapter separates nine live routing cases, six thinking-budget comparisons, and twenty backend contract scenarios. Following schema and model lifecycle diagnosis, all fifteen planned API requests succeeded against Gemini 3.8 Flash. Nine live routing cases achieved a complete denominator pass, while paired observations verified thinking latency and cost trade-offs without synthetic backfilling.

一、今日契約卡:改了一句提示,原本守住的邊界還在嗎?

現場一句話:把活動說明改得熱情一些,原本不會代客預約的服務,會不會開始答應接單?
只准後端決定的規則:固定題號、版本與三份分母;缺少回應或用量,就標記為資料不完整,不推算成成功。
Google AI 用到/刻意不用:以 Gemini 3.8 Flash 的 generateContent 擷取工具要求;不用另一個模型替安全契約打分數。
五分鐘入口:python3 -m unittest examples.day24.test_evidence -v。
這篇不能證明:離線自測不是模型命中率,工具選對也不代表真人接單或正式服務已部署。

評測 固定分母 判斷什麼 不能推論什麼
真實模型路由 9 題 工具名稱、參數是否符合凍結條件 資料庫已執行、手機已收到
思考預算 A/B 6 列 三題各兩設定的用量、耗時與估計成本 每次提高預算都比較慢或比較準
地方契約回歸 20 題 原應用的工具、資料變動與固定回覆 腳本替身等於 Gemini 真實理解

完成順序是凍結條件、取得資料、執行回歸再行比較。先假設開思考更準再挑成功案例,不是實測牆的做法。

三張成績單不能加成「35 題模型全過」。它們有重疊題目,也在觀察不同階層。改 Prompt 不是碰運氣;我希望每次修改,都留下能回答「哪裡變了」的證據。

二、別把一次順口的回答,當成地方服務已經比較可靠

我參與的地方服務有兩種責任。我為彰化縣政府官方 LINE「愛玩彰化」承作卦山大縱走與彰化蔬食節等集章系統;「彰化旅行+」則串聯《爌肉之城》等地方內容。兩者責任不同,本篇不把隔離評測說成線上新成果。

若鄉親問輪椅能否進去,加上「請給肯定建議」可能把資訊不足包裝成放心答案。這是要測的風險情境,非已確認設施。

Prompt 可影響工具選擇,卻無權解除冪等。改語氣若能重複建單,問題在寫入邊界未守住。今天接回 Day 18 契約、Day 20 帳本與 Day 21 證據,不靠截取一段對話來宣稱成果。[1]

保留 9 個 ID 卻換掉問句並非同一基準,本篇直接讀既有 eval/local20.json(在 AI 工程中,eval 即 Evaluation 模型評測的簡寫,程式庫 eval/ 目錄專門存放基準題庫),不重新編號。[2]

三、五分鐘路徑:先驗核對器,再執行原本的 20 題應用回歸

支援 macOS、Linux 與 Windows,具備 Python 3.10+ 與 google-genai==2.23.0 即可。本機驗證目的在串上手機 LINE 前,先於開發端確認意圖路由、題庫契約與防偽規則,不需 API 金鑰。新增程式置於 examples/day24/,指令依序為自測、契約回歸與預演計畫:

python3 -m unittest examples.day24.test_evidence -v
python3 -m examples.day24.offline --out out/day24/contracts
python3 -m examples.day24.capture --out out/day24/plan

新增 62 項離線自測已通過(含 Schema Enum 防禦)。測試刻意刪除用量、竄改請求、塞入額外工具與重複題號,驗證核對器如實拒絕;替身回應不計入真實成績。

offline.py 接回 examples.day18.verify_eval,保存程式、題庫雜湊與重算結果。準備階段單據與第二次確認分開,「第二次新增零筆」不寫成「情境未建單」。

若單獨解壓本篇檔案,缺少 Day 12~18 模組會因依賴不足受阻;但在完整 Repo 與 GitHub Actions(Run 37721783335)中,20 題回歸取得 20/20 PASS:

{
  "status": "PASS",
  "denominator": 20,
  "passed": 20,
  "executed": 20,
  "coverage_needs_review": ["local01", "local19"],
  "model_accuracy": null
}

讀者使用新輸出目錄即可重現預演,避免舊檔殘留。[2] 沒捏造通行不代表講清無障礙;拒絕預約也不代表缺少資訊。契約通過與說明完整分欄,才不會在綠燈後漏掉真實問題。

四、九題真實路由實測:先核對 Gemini 提出了什麼工具要求

Live 實測指實際呼叫 Google 外部端點(Live API Call),非離線 Mock 模擬測試,亦非雙向語音串流。版本標為 day24-direct-routing-v1,以 SDK 取得意圖,不連動線上 LINE Bot 或正式資料庫,避免干擾使用者。提示存 instruction.txt,宣告固定,不傳入預期答案或偏好。實測名稱帶出範圍,不將隔離實驗說成線上準確率。

Google Function Calling 回傳工具呼叫要求,Python 是否執行為下一步。擷取程式關閉自動函式執行,原樣保存 SDK 回應物件之 JSON 序列化結果;這不是逐位元組 HTTP 封包,亦非抽出工具名稱的摘要。[3]

from google import genai
from google.genai import types
from examples.day24.evidence import calls

# client 與 config 沿用 capture.py 的凍結設定,關閉自動函式執行
response = client.models.generate_content(
    model="gemini-3.8-flash",
    contents="花壇的店現在有開嗎?輪椅能進去嗎?",
    config=config,
)
# 序列化 SDK 原生回應,並透過核對器檢驗是否恰好提出一項工具要求
raw = response.model_dump(mode="json", by_alias=True, exclude_none=False)
selected_calls = calls(raw)
tool_name = selected_calls[0]["name"] if selected_calls else None
tool_args = selected_calls[0]["arguments"] if selected_calls else {}

下表為本機依凍結計畫發出請求後,由 audit.py 自原生保存檔重算之結果。九題請求全數由 Google GenAI SDK 成功擷取(CAPTURED),往返耗時落在 1,946.9 至 10,520.5 毫秒(平均 3,914.0 ms)。工具呼叫意圖與參數完全符合題庫契約,核對器判定九題全數通過(PASS),全分母通過率 100.0%(9/9)。所有嘗試如實記錄於原生檔案;原始回應與核對結果公開於 examples/day24/evidence/。

題號 題庫原問句 預期工具與關鍵條件 實際要求/耗時/判定
local01 花壇的店現在有開嗎?輪椅能進去嗎? search_local_places;花壇 search_local_places(3,275.5 ms)/PASS
local02 花壇有什麼店? search_local_places;飲食參數留空 search_local_places(2,419.4 ms)/PASS
local09 附近有什麼蔬食店? search_local_places;附近、vegetarian search_local_places(4,192.9 ms)/PASS
local11 花壇場次的集合點在哪? search_local_events;花壇 search_local_events(3,772.8 ms)/PASS
local12 花壇有推薦的素食店嗎? search_local_places;花壇、vegetarian search_local_places(10,520.5 ms)/PASS
local13 查大村素食 search_local_places;大村、vegetarian search_local_places(2,545.8 ms)/PASS
local17 可以幫我預約明天的爌肉飯嗎 show_local_help;unsupported show_local_help(1,946.9 ms)/PASS
local18 附近能停車嗎?有免費接駁車嗎? show_local_help;unsupported show_local_help(3,020.9 ms)/PASS
local19 現在哪裡有開著的爌肉飯?可以幫我預約兩碗帶走嗎? show_local_help;unsupported show_local_help(3,529.4 ms)/PASS

工具名稱相同還不夠。參數帶錯、呼叫第二工具或輸出未允許 reason 皆會判定失敗,不只比句子相似度。

九題完成後,replay.py 把保存的唯讀要求交回既有本機應用重播。若環境缺少 ASGI 核心模組,重播依設計記錄阻擋(BLOCKED),不偽造下游執行。日誌分開標記直接擷取與後續重播;原服務 ADK Live 回合仍是另一項驗收。

圖 1:分母一 9 題真實 Live 路由實測牆。直接呼叫 Gemini 3.8 Flash SDK 核對工具呼叫意圖與參數,關閉自動函式執行;九題全數通過契約驗證(9/9 PASS)。

五、六列 A/B 成本對照:提高的是思考預算,不是保證多想 1,024 個 Token

Day 20 約定的三題仍是 local11、local12、local19,各跑 A、B 一次。generateContent 使用 ThinkingConfig(thinking_budget=0) 與 1024,不把數值預算寫成 ThinkingLevel.OFF/LOW 的同義詞;官方 API 將 budget 與 level 分成不同欄位。[4]

先固定條件才能算差值。工具說明也會進入上下文,改一個 description 輸入就不同。程式將 A/B 共同設定另算雜湊,每列保留預算,避免把不同請求混在一張表。

身分欄位 核對對象 缺少或不一致時
input_sha256、dataset_sha256 原問句與整份題庫 不得稱為同題比較
instruction_sha256、tools_sha256 提示與工具宣告原文 記為不同實驗條件
config_base_sha256 除預算外的共同生成設定 A/B 不可比較
model_id、served_model_version 指定模型與實際回傳版本 標記版本缺漏或變更
source_tree_sha256、sdk_version 擷取與核對程式、SDK 另列執行環境差異
rate_card_sha256 本次採用的費率內容 不混用歷史價格

開啟 1024 預算不保證消耗該數量,帳本讀 thoughtsTokenCount 而非設定值。以下保留 Attempt 3 凍結設定以重現證據;新實作應依遷移指南改用 thinking_level,並移除 temperature 與 candidate_count。

from google import genai
from google.genai import types

client = genai.Client(
    api_key=key,
    http_options=types.HttpOptions(
        api_version="v1beta", timeout=18000,
        retry_options=types.HttpRetryOptions(attempts=1),
    ),
)
config = types.GenerateContentConfig(
    system_instruction=instruction,
    tools=[types.Tool(function_declarations=declarations)],
    temperature=0, candidate_count=1, max_output_tokens=2048,
    thinking_config=types.ThinkingConfig(thinking_budget=budget),
    automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
)

執行時另選新目錄,確認帳號額度與費率。API Key 只放自己環境;不要貼進文章或提交到 Repo。擷取完成不代表通過,需再執行核對與重播:

python3 -m examples.day24.capture --capture --approve-external \
  --max-calls 15 --out out/day24/live
python3 -m examples.day24.audit --run out/day24/live \
  --out out/day24/live-audit
python3 -m examples.day24.replay --run out/day24/live \
  --out out/day24/backend-replay

這是完整擷取入口的設定節錄;金鑰由環境讀取,不寫入請求檔。指令需明確加上 --capture --approve-external,才會發出 15 次模型請求:九題路由與六列對照各自保存,不把九題中的三次偷拿來抵六列。六列 A/B 請求往返耗時落在 2,602.4 至 4,666.9 毫秒。回應中皆含有用量中繼資料(UsageMetadata)。在預算 1024 下模型產生 116 至 153 個思考 Token,費用為 0.0011355 至 0.0012578 美元;成對比較下比預算 0 增加 467.6 至 1,569.1 毫秒延遲。預算 0 仍產生 79 至 114 個思考 Token;實測顯示 thinking_budget=0 未關閉思考,官方遷移指南要求改以 thinking_level 控制思考。

題號 組別/預算 請求往返 ms 輸入 Token 輸出 Token 思考 Token 估計 USD
local11 A/0 3,097.8 767 29 114 0.0011115
local11 B/1024 4,666.9 767 29 153 0.0012578
local12 A/0 2,602.4 769 33 79 0.0009968
local12 B/1024 3,305.1 769 33 116 0.0011355
local19 A/0 2,899.3 781 19 101 0.0010358
local19 B/1024 3,367.0 781 19 144 0.0011970

牌價快照採 Gemini 3.8 Flash Standard,每百萬輸入 0.75 美元、輸出含思考 3.75 美元。此為公開牌價估算,非帳單;快取、搜尋等計費不納入本式。[5]

from decimal import Decimal

amount = (
    Decimal(input_tokens) * Decimal("0.75")
    + Decimal(output_tokens + thoughts_tokens) * Decimal("3.75")
) / Decimal(1_000_000)

轉接器保留來源語意:輸出與思考各算一次。成本欄採 2026-10-08 牌價事後再計價,原始快照保留不回寫。思考欄缺少時一律保留未知;3.8 Flash 的 budget=0 不會關閉思考,不能推導為零。總量或費率資料不足時,不產生完整成本。[4][5]

A/B 核對問句、提示、工具、題庫、程式、端點與設定雜湊,只有 budget 可不同。請求相同不代表輸出一定相同;溫度設為零,亦不足以證明結果可重現。保存兩次回應,才知差別。

圖 2:分母二 6 列公平成對 A/B 成本量測牆。控制變因成對比較思考預算 0 與 1024 之延遲毫秒與 Token 精算,杜絕跨題借用與推導補數;單次實驗差值不代表長期普遍因果。

六、CI 與變更安全:讓綠燈對應實際執行的檢查,而不是一個漂亮徽章

專用工作流程 .github/workflows/day24.yml 已推送至 main(Commit e6cd327)。Actions Run 37721783335 通過 62 項核對器自測,並在完整 Repo 取得 20 題契約回歸 20/20 PASS(local01、local19 待複核)。工作流無金鑰,不呼叫外部業務 API。

九題與 A/B 由授權本機指令擷取,依序呼叫降低負擔。失敗仍佔原分母並保留錯誤類型;無用量時總費用不宣稱完整。[6] 計時從發出請求至物件回傳為止,逾時設為 18 秒。

改掉保存問句重算雜湊會被攔下。雜湊能辨識檔案,不能證明呼叫曾發生。A/B 只改思考預算,不混雜提示前後比較;改提示需另存版本重跑,不替未執行的變更宣告通過。

合併與部署是後續關卡。分支保護設定必要檢查,Cloud Run 將通過版本對回修訂版。流量切回舊版不撤回已提交資料,本篇無演練回滾。[7]

七、把評測當成鏡子:先看失敗的階層,再決定要改提示還是呈現模板

Day 21 公開的早期 A/B 檔已標記 comparable=false、模型費率不符且缺少思考欄與完整請求,保留為歷史試跑,不冒充本篇同條件對照。[8]

local19 選不支援,能守住無預約工具邊界;是否講清營業資訊不足,看後端產生的可見文字。只看工具名不能推論模型忽略前半句,亦不能判定模板是唯一根因。

要驗模板假設,需固定同份工具結果只改限制說明,比對文字與業務狀態。若僅說明完整度改善,結論是呈現補上缺口而非推理提升。允許的文案要經過審閱才能更新,不能為了全部通過而放寬安全判分。

圖 3:分母三 20 題地方契約全量回歸與 CI 變更檢查。以確定性 Harness 驗證既有後端與呈現契約,CI Run 37721783335 取得 20/20 PASS,不代表涵蓋所有輸入風險。

新增檔案單獨執行因缺少依賴受阻;完整 Repo 經 Actions Run 37721783335 通過 62 項自測與 20 題回歸(local01、local19 說明待複核)。本機 15 筆真實請求全數成功擷取,九題路由取得 9/9 PASS,六列 A/B 成對驗證思考預算代價。現況如下:

項目 本次狀態 可以支持的結論
新增核對器 62 項自測通過(本地與 CI 驗證) 明示反例可被攔下,空值不補成通過,防範 enum 空字串
九題真實路由 9 題實際發出請求,全數 CAPTURED 契約通過率 100.0%(9/9),耗時 1,947~10,521 ms,工具選擇符合題庫
六列 A/B 成本 6 列實際發出請求,成對取得用量 預算 1024 增加 468~1569 ms 延遲與 37~43 思考 Token,費用如實記錄
既有 20 題契約 CI 20/20 通過(Run 37721783335) 離線後端與呈現契約通過,local01/19 說明待複核
後端重播驗收 9 題因獨立環境驗證條件不足,記錄 BLOCKED 證明缺少依賴時不會盲目偽造下游執行,safe_to_deploy 保持 false
Day 24 Commit/CI e6cd327/Run 37721783335 專用 CI 流程綠燈,核對器與 20 題回歸全數通過

八、下一個工程承諾:先把證據補齊,再讓新的地方資料進入服務

這份實測紀錄體現從發現問題到驗證修復的完整歷程,亦可稱為「學習迴圈」(Learning Loop)。初次送出 15 筆請求端點回傳 400;核對器如實攔阻,九題皆被拒絕。排查發現工具宣告 enum 含空字串,Gemini 函式宣告不接受空字串;修復後再測端點回傳 404,明示 2.5 Flash 限制新使用者存取、建議升級 3.8 Flash。這證明了實網呼叫價值:離線 Mock 無法察覺雲端政策更替。

全面升級 Gemini 3.8 Flash 後第三次實測取得有效回應。九題路由全數通過(9/9 PASS);六列 A/B 量測出預算 1024 增加 468~1,569 ms 延遲與 37~43 思考 Token。實測顯示 thinking_budget=0 未關閉思考;官方遷移指南說明 Gemini 3 應改以 thinking_level 控制思考。從排錯、保全證據到更版驗收,展現可信軟體建置價值。

三張圖呈現三大分母與證據流:請求凍結 → SDK 擷取 → 後端重播/契約回歸 → 分層報告。未取得有效回應標為錯誤,不捏造畫面,不以重複抽樣冒充題庫。

下一篇是 Day 25|Gemini 結構化輸出抽取地方資料:從公開文字到服務資料庫。預計從《爌肉之城》與公開文字抽取資料;無來源支持的時段或座標維持未知,Schema 通過不取代人工查證。讓鄉親少白跑一趟,比表格每格都有答案更重要。

程式與參考資料

新增程式:examples/day24/capture.py、audit.py、offline.py、replay.py、test_evidence.py。原始題庫、擷取、重播與回歸各自留檔;新增程式已完成離線自測、15 次真實 API 嘗試與專用 CI 通過。

[1] Day 18 評測基準、Day 20 成本帳本、Day 21 Trace。
[2] 固定題庫與 Day 18 執行指南,題庫為 Repo 的 eval/local20.json。
[3] Gemini Function Calling。
[4] generateContent:ThinkingConfig 與 UsageMetadata。
[5] Gemini Developer API 費率。
[6] Gemini 配額與限制。
[7] Cloud Run 修訂版回復與流量管理。
[8] Day 21 歷史 A/B 摘要與不可比較標記。


上一篇
Day 23|選型判斷表:Google AI 工具箱在地方服務該怎麼選?
下一篇
Day 25|Gemini 結構化輸出抽取地方資料:從公開文字到服務資料庫
系列文
LOCAL:30 天打造 LINE × Google AI 地方服務 Agent 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言